package com.example.api2interface.filter;

import com.example.api2interface.entity.RequestHeader;
import com.example.api2interface.entity.ResultData;
import com.example.api2interface.entity.ReturnCode;
import com.example.api2interface.utils.*;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.SortedMap;

// Spring Boot保证接口安全
public class SignFilter implements Filter {

    @Resource
    private RedisUtil redisUtil;

    // 从filter配置中获取sign过期时间
    private Long signMaxTime;

    private static final String NONCE_KEY = "x-nonce-";

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
        HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;

        // 返回完整的URL
        System.out.println(httpRequest.getRequestURI());

        // 拿到封装后的Request
        CacheBodyHttpServletRequest requestWrapper = new CacheBodyHttpServletRequest(httpRequest);

        // 构建请求头
        RequestHeader requestHeader = new RequestHeader();

        requestHeader.setSign(httpRequest.getHeader("X-Sign"));
        requestHeader.setNonce(httpRequest.getHeader("X-Nonce"));

        String xTime = httpRequest.getHeader("X-Time");
        // X-Time为空
        if (StringUtils.isEmpty(xTime)) {
            this.responseFail(httpResponse, ReturnCode.ILLEGAL_HEADER);
            return;
        }
        // 不为空
        requestHeader.setTimestamp(Long.parseLong(xTime));

        // 验证请求头是否存在
        if (StringUtils.isEmpty(requestHeader.getSign()) || ObjectUtils.isEmpty(requestHeader.getTimestamp()) || StringUtils.isEmpty(requestHeader.getNonce())) {
            this.responseFail(httpResponse, ReturnCode.ILLEGAL_HEADER);
            return;
        }

        /*
         * 1.重放验证
         * 判断timestamp时间戳与当前时间是否超过60s（过期时间根据业务情况设置）,如果超过了就提示签名过期。
         */
        long nowTime = System.currentTimeMillis() / 1000;
        if (nowTime - requestHeader.getTimestamp() > signMaxTime) {
            this.responseFail(httpResponse, ReturnCode.OVERTIME_ERROR);
            // 结束程序，避免还往下走
            return;
        }

        // 2.判断nonce
        boolean nonceExists = redisUtil.hasKey(NONCE_KEY + requestHeader.getNonce());
        if (nonceExists) {
            // 存在：请求重复
            this.responseFail(httpResponse, ReturnCode.REPLAY_ERROR);
            return;
        } else {
            // 不存在：放入redis
            redisUtil.set(NONCE_KEY + requestHeader.getNonce(), requestHeader.getNonce(), signMaxTime);
        }

        // 验证签名
        boolean accept;
        SortedMap<String, String> paramMap;
        switch (httpRequest.getMethod()){
            case "GET":
                paramMap = HttpDataUtil.getUrlParams(requestWrapper);
                accept = SignVerifyUtil.verifySign(paramMap, requestHeader);
                break;
            case "POST":
                paramMap = HttpDataUtil.getBodyParams(requestWrapper);
                accept = SignVerifyUtil.verifySign(paramMap, requestHeader);
                break;
            default:
                accept = true;
                break;
        }
        if (accept) {
            filterChain.doFilter(requestWrapper, servletResponse);
        } else {
            this.responseFail(httpResponse, ReturnCode.ARGUMENT_ERROR);
        }
    }

    private void responseFail(HttpServletResponse httpResponse, ReturnCode returnCode) {
        ResultData<Object> returnData = ResultData.fail(returnCode.getCode(), returnCode.getMessage());
        WebUtil.writeJson(httpResponse, returnData);
    }

    /**
     * 过滤器初始化方法：当Web容器启动并初始化过滤器时，会调用此方法
     *
     * @param filterConfig 过滤器配置对象，包含了过滤器的初始化对象参数
     * @throws ServletException
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String s = filterConfig.getInitParameter("signMaxTime");
        this.signMaxTime = Long.parseLong(s);
    }
}
